iT邦幫忙

2022 iThome 鐵人賽

DAY 26
0

今天是繼承的最後一篇,介紹另一個在物件導向程式語言常常討論的議題:繼承組合之比(或之爭)。


  • 物件導向領域的繼承機制,父子類別間的關係,常會用「子類別is a父類別」來形容。以我們一直沿用的Tree為例,父類別是Tree,子類別有HardwoodConifer,另外孫輩Timba多重繼承自HardwoodConifer。用is a來「造句」,就是A Hardwood is a Tree.A Confider is a Tree.A Timba is a Hardwood.,以及A timba is a Confifer。隔代的A Timba is a Tree.也是成立的。這幾句英語非常容易讓我們理解類別之間的關係。

  • 至於今天的重頭戲組合,英文是composition,這是名詞,動詞則為composite

  • 組合是另一種類別間的關係:兩個或多個類別有相關,但並不是父子間的「垂直」血源關係,而是「擁有」或「包含」的關係,好比公司擁有員工、汽車擁有零件、樹擁有根幹枝葉等。換個詞兒可以這樣說:員工是組成公司的一部分、零件是汽車的一部分、根幹枝葉則都是樹的一部分。

  • 在繼承體系中,子類別自動將父類別的所有屬性和方法(私有者例外)加到他自己之中。但組合則沒有這種情形,兩(或多)個類別你是你、我是我。雖然你擁有我,但我倆依然互相獨立,資源互用當然沒有問題,不過你的資源不會融入到我身上,我也不會把我的寶貝奉送給你。

  • 也就是說,透過組合,類別之間不存在「我泥中有你,你泥中有我」的「你儂我儂」。它們是「協作」,一起完成某些事情,或者一起製造某些產品。

  • 通常我們以「類別Ahas a類別B」(註1)來形容組合。例如樹的主要部份是根、幹、枝、葉,我們就可以說A tree has roots., A tree has a trunk., A tree has branches.以及A tree has leaves

  • 換成中文,繼承用「」:闊葉樹是樹、針葉樹是樹、白馬是馬...。組合則用「」:樹有根、樹有幹、樹有枝、樹有葉、馬有蹄...。中文比英文簡潔,且不必管單複數。然而為了「國際接軌」,下文筆者還是用英文'is a'和'has a'來區別兩者。

  • 再補充兩個術語:如果類別A has a 類別B。這時類別A通常稱為composite類別,而類別B則叫做component類別

  • "Talk is cheap. Show me the code.":

    class Tree():
        def __init__(self, breed: str, age: int, height: int, root_system: str, leaf_type):
            self.__breed = breed
            self.__age = age
            self.__height = height
    
            self.__roots = Roots(root_system)
            self.__trunk = Trunk()
            self.__branches = Branches()
            self.__leaves = Leaves(leaf_type)
    
        @property
        def breed(self) -> str:
            return self.__breed
    
        @property
        def age(self) -> int:
            return self.__age
    
        @property
        def height(self) -> int:
            return self.__height
    
        @property
        def roots(self):
            return self.__roots
    
        @property
        def leaves(self):
            return self.__leaves
    
        def grow(self): 
            ...
    
        def reproduce(self):
            ...
    
    
    class Roots():
        def __init__(self, root_system: str):   # constructor
            self.__root_system = root_system    # three(3) types of root systems: taproot, fibrous, adventitious
    
        @property
        def root_system(self) -> str:
            return self.__root_system
    
        def absorb_water(self):
            print(f'{__class__.__name__} {self.root_system} is absorbing water.')
    
        def swarm(self):
            print(f'{__class__.__name__} {self.root_system} is swarming.')
    
    
    class Leaves():
        def __init__(self, leaf_type: str): 
            self.__leaf_type = leaf_type   # three(3) basic leaf types: needles, scales and broadleaf.
    
        @property
        def leaf_type(self) -> str:
            return self.__leaf_type
    
        def photosynthesize(self):  # 進行光合作用。
            print(f'{__class__.__name__} {self.leaf_type} is photosynthesizing.')
        ...   
    
    
    class Trunk():
        ...
    
    
    class Branches():
        ...   
    
  • 可以這樣使用:

    tree = Tree('cedar', 159, 1_900, 'fibrous', 'needles')
    print(f'''
    {tree.breed = :8}{tree.age = :<8,}{tree.height = :<8,}
    {tree.roots.root_system = :10}{tree.leaves.leaf_type = }''')
    print()
    tree.roots.absorb_water()
    tree.roots.swarm()
    print()
    tree.leaves.photosynthesize()
    
  • 輸出:
    https://ithelp.ithome.com.tw/upload/images/20221011/201484855OhtnWB6cp.png

  • 也許您會說,上例的整棵樹和其根、幹、枝、葉本來就有一定關係。根、幹、枝、葉都是樹的構造成分,這個比喻不知有無曲解了組合的原義。如果有此顧慮,筆者就換一個情境好了。依然是樹,這次每棵樹上都有一間不同顏色的小木屋(樹屋),另外每棵樹都綁著數量不同、或多或少的黃絲帶。小木屋和黃絲帶本來和樹毫不相干,我們試用組合方式將這三者結合:

    class Tree():
        def __init__(self, breed: str, age: int, height: int, cabin_color: str='white', ribbon_amount: int=0):
            self.__breed = breed
            self.__age = age
            self.__height = height
            self.__cabin = Cabin(cabin_color)
            self.__ribbon = YellowRibbon(ribbon_amount)
    
        @property
        def breed(self) -> str:
            return self.__breed
    
        @property
        def age(self) -> int:
            return self.__age
    
        @property
        def height(self) -> int:
            return self.__height
    
        @property
        def cabin(self) -> str:
            return self.__cabin
    
        @property
        def ribbon(self) -> int:
            return self.__ribbon
    
        def grow(self): 
            ...
    
        def reproduce(self):
            ...
    
    
    class Cabin():
        def __init__(self, color: str):   # constructor
            self.__color = color 
    
        @property
        def color(self) -> str:
            return self.__color
    
        def provide_shelter(self):
            print(f'The {self.color} cabin is providing shelter.')
    
    
    class YellowRibbon():
        def __init__(self, amount: int): 
            self.__amount = amount   # 掛多少條黃絲帶。
    
        @property
        def amount(self) -> int:
            return self.__amount
    
        def forgive(self):
            print(f'We have {self.amount} {__class__.__name__}(s) as a sign of forgiveness.')
    
  • 測試程式:

    tree = Tree('cedar', 159, 1_900, cabin_color='red', ribbon_amount=500)
    print(f'''
    {tree.breed=:10}{tree.age=:<10,}{tree.height=:<,}
    {tree.cabin.color=:12}{tree.ribbon.amount=}''')
    print()
    tree.cabin.provide_shelter()
    print()
    tree.ribbon.forgive()
    
  • 輸出:
    https://ithelp.ithome.com.tw/upload/images/20221011/20148485UYEasp9Z2b.png

繼承和組合的異同

  • 繼承和組合兩者都是物件導向世界的重要機制,而且都有程式碼再利用(Code Reusability)的妙用,這是兩者相同的地方。
  • 兩者不同處,在於使用繼承時,子類別除了取得父類別的資源外,還可加以修改(利用overriding)和擴充父類別不足之處;如採用組合,composite類別是「純使用」component類別,無法修改或擴充。
  • 繼承較耗用資源與效能。

兩者使用時機

  • 從以上的異同,很容易得出兩者的使用時機:
    • 如果在類別中只想使用其他類別,沒有修改擴充的需要,請用組合
    • 反之,如有修改擴充需要,則用繼承
    • 系統要達到物件導向的精粹Polymorphism多型,先決條件是使用繼承。
  • 所以這兩種物件導向的機制,各有應用場合。

繼承和組合之爭

  • 組合常被認為是loosely coupled(低耦合),而繼承則為strongly coupled(高耦合)。
  • 也有人說繼承在某種程度上破壞了封裝原則。
  • 組合比繼承好」的說法甚囂塵上。筆者倒認為兩者使用時機不同,該用誰就用誰,沒有必要刻意排斥或追捧哪一個。
  • 最重要的原則是:欲使用繼承,請先確認類別間是不是有is a的關係。如果不是is a而是has a,請使用組合

註1:如果是「一對多」的組合關係,類別B改用複數就行。


上一篇
還是Abstract...
下一篇
Polymorphism「現形」記
系列文
Oops! OOPP: An Introduction to Object-Oriented Programming in Python30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言